package com.frejoys.common.service;


import cn.hutool.core.codec.Base64;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.img.ColorUtil;
import cn.hutool.core.img.ImgUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.qrcode.QrCodeUtil;
import cn.hutool.extra.qrcode.QrConfig;
import cn.hutool.json.JSONObject;
import com.aliyun.oss.common.utils.BinaryUtil;
import com.aliyun.oss.model.GeneratePresignedUrlRequest;
import com.aliyun.oss.model.MatchMode;
import com.aliyun.oss.model.PolicyConditions;
import com.frejoys.common.config.UploadConfig;
import com.frejoys.common.config.ValueConfig;
import com.frejoys.common.constant.CommonConstant;
import com.frejoys.common.error.HttpStatus;
import com.frejoys.common.exception.GeneralException;
import com.frejoys.common.util.CacheKeyUtil;
import com.frejoys.common.util.JwtUtil;
import com.frejoys.common.util.OssUtil;
import com.frejoys.common.util.RedisUtil;
import com.frejoys.common.util.TimeUtil;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import org.springframework.util.ResourceUtils;
import org.springframework.web.multipart.MultipartFile;

import javax.imageio.ImageIO;
import javax.imageio.stream.ImageOutputStream;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * 阿里云存储服务
 */
@Component
@RequiredArgsConstructor
public class AliStorageService {

    private final ValueConfig valueConfig;

    private final RedisUtil redisUtil;

    /**
     * 获取按年生成文件名称
     * @return
     */
    public String getFileName() {
        return DateUtil.year(DateUtil.date()) + "/" + IdUtil.fastSimpleUUID();
    }

    /**
     * 获取按年生成文件名称，在末尾加上自定义字符
     * @return
     */
    public String getFileName(String str) {
        return getFileName() + str;
    }

    /**
     * 上传文件到阿里云oss
     * @param file
     * @return
     */
    public String uploadFileToOss(MultipartFile file) {
        if (file.isEmpty()) {
            throw new GeneralException(HttpStatus.fileNoExist);
        }

        if ((file.getSize() / 1024 / 1024) > valueConfig.getAliOssBucketUploadSize()) {
            throw new GeneralException(HttpStatus.fileBig);
        }

        if (!UploadConfig.fileTypes.contains(file.getContentType())) {
            throw new GeneralException(HttpStatus.fileNoType);
        }

        String dirFileName = getFileName(file.getOriginalFilename().substring(file.getOriginalFilename().lastIndexOf(".")));

        dirFileName = valueConfig.getAliOssBucketUploadPath() + dirFileName;
        try {
            OssUtil.ossClient.putObject(valueConfig.getAliOssBucketUploadBucket(), dirFileName, file.getInputStream());
        } catch (Exception e) {
            e.printStackTrace();
            return "";
        }

        return dirFileName;
    }

    /**
     * 上传文件到阿里云oss
     * @param file
     * @param fileName
     * @return
     */
    public String uploadFileToOss(InputStream file, String fileName) {

        String dirFileName = StrUtil.isNotEmpty(fileName) ? fileName : getFileName(".png");

        dirFileName = valueConfig.getAliOssBucketUploadPath() + dirFileName;
        try {
            OssUtil.ossClient.putObject(valueConfig.getAliOssBucketUploadBucket(), dirFileName, file);
        } catch (Exception e) {
            e.printStackTrace();
            return "";
        }

        return dirFileName;
    }

    /**
     * 删除阿里云oss文件
     * @param fileName
     * @return
     */
    public Boolean deleteFileToOss(String fileName) {
        if (StrUtil.isEmpty(fileName)) {
            return true;
        }

        try {
            OssUtil.ossClient.deleteObject(valueConfig.getAliOssBucketUploadBucket(), fileName);
        } catch (Exception e) {
            e.printStackTrace();
        }

        return true;
    }

    /**
     * 获取直传签名
     * @param suffix 文件后缀 .png
     * @return
     */
    public Map<String, String> getPolicy(String suffix) {

        Map<String, String> respMap = new LinkedHashMap<>();

        try {

            // 超时时间
            long expireTime = 60;
            long expireEndTime =  TimeUtil.currentTimeMillis() + expireTime * 1000;

            // 文件最终名称
            String filename = valueConfig.getAliOssBucketUploadPath() + getFileName() + suffix;

            PolicyConditions policyConds = new PolicyConditions();
            policyConds.addConditionItem(PolicyConditions.COND_CONTENT_LENGTH_RANGE, 0, 1048576000);
            policyConds.addConditionItem(MatchMode.StartWith, PolicyConditions.COND_KEY, valueConfig.getAliOssBucketUploadPath());

            String postPolicy = OssUtil.ossClient.generatePostPolicy(DateUtil.date(expireEndTime), policyConds);
            byte[] binaryData = postPolicy.getBytes("utf-8");
            String encodedPolicy = BinaryUtil.toBase64String(binaryData);
            String postSignature = OssUtil.ossClient.calculatePostSignature(postPolicy);


            respMap.put("accessid", valueConfig.getAliOssBucketUploadAccessKeyID());
            respMap.put("policy", encodedPolicy);
            respMap.put("signature", postSignature);
            respMap.put("dir", valueConfig.getAliOssBucketUploadPath());
            // respMap.put("host", "http://" + valueConfig.getAliOssBucketUploadBucket() + "." + valueConfig.getAliOssBucketUploadEndpoint());
            // respMap.put("host", "http://" + valueConfig.getAliOssBucketUploadEndpoint());
            respMap.put("host", valueConfig.getAliOssBucketUploadHttp() + valueConfig.getAliOssBucketUploadEndpoint());
            respMap.put("expire", String.valueOf(expireEndTime / 1000));

            JSONObject jasonCallback = new JSONObject();
            jasonCallback.put("callbackUrl", valueConfig.getAppUrl() + valueConfig.getAliOssBucketUploadNotifyUrl());
            // jasonCallback.put("callbackBody", "filename=${object}&size=${size}&mimeType=${mimeType}&height=${imageInfo.height}&width=${imageInfo.width}");
            // jasonCallback.put("callbackBodyType", "application/x-www-form-urlencoded");

            // 自定义加密
            Map<String, Object> map = new HashMap();
            map.put(CommonConstant.fname, filename);

            jasonCallback.put("callbackBody", StrUtil.format("""
                    {
                        "filename": "${object}",
                        "size": "${size}",
                        "mimeType": "${mimeType}",
                        "height": "${imageInfo.height}",
                        "width": "${imageInfo.width}",
                        "token": "{}"
                    }
                    """, JwtUtil.createToken(map, valueConfig.getUploadJwtCallbackSecret(), valueConfig.getUploadJwtCallbackExp())));

            // jasonCallback.put("callbackBody", "{\"filename\":${object}, \"size\": ${size}, \"mimeType\": ${mimeType}, \"height\": ${imageInfo.height}, \"width\": ${imageInfo.width}");
            jasonCallback.put("callbackBodyType", "application/json");


            String base64CallbackBody = BinaryUtil.toBase64String(jasonCallback.toString().getBytes());
            respMap.put("callback", base64CallbackBody);

            // 将名称放入返回的json，客户端使用
            respMap.put(CommonConstant.fname, filename);

        } catch (Exception e) {
            e.printStackTrace();
        }

        return respMap;
    }

    /**
     * 文件签名
     * @param filename
     * @return
     */
    public String fileSignature(String filename, String style) {
        String key = CacheKeyUtil.fileCacheKey(filename, style);
        Object obj = redisUtil.get(key);
        if (obj != null) {
            return obj.toString();
        }

        GeneratePresignedUrlRequest req = new GeneratePresignedUrlRequest(valueConfig.getAliOssBucketUploadBucket(), filename);
        req.setExpiration(DateUtil.date(TimeUtil.currentTimeMillis() + valueConfig.getAliOssBucketAccessExpire()));
        req.setProcess(style);
        String url = OssUtil.ossClient.generatePresignedUrl(req).toString();

        redisUtil.set(key, url, (valueConfig.getAliOssBucketAccessExpire() / 1000L) - 10L);

        return url;
    }

    /**
     * 获取二维码中间logo图
     * @return
     */
    public BufferedImage getLogoBufferedImage() {
        InputStream inputStream = null;
        // 通过类加载器的getResourceAsStream方法，让jar读取到自己的资源文件
        try {
            inputStream = this.getClass().getClassLoader().getResourceAsStream("static/logo.png");
            return ImageIO.read(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return null;
        // try {
        //     return ResourceUtils.getFile(valueConfig.getLogo()).getPath();
        // } catch (FileNotFoundException e) {
        //     e.printStackTrace();
        // }
        // return "";
        // try {
        // return ResourceUtils.getFile(valueConfig.getLogo()).getPath();
        // return this.getClass().getResource("/static/logo.png").getPath();
        // } catch (FileNotFoundException e) {
        //     e.printStackTrace();
        // }
        // return "";
    }

    /**
     * 生成二维码图片并上传
     * @param url
     * @return
     */
    public String uploadQrCodeImage(String url) {
        String fileName = "";
        QrConfig config = new QrConfig(800, 800);
        // 高纠错级别
        config.setErrorCorrection(ErrorCorrectionLevel.H);

        BufferedImage generate = QrCodeUtil.generate(url, config);
        try {
            // 转换流信息写出
            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
            ImageIO.write(generate, "png", outputStream);

            fileName = uploadFileToOss(new ByteArrayInputStream(outputStream.toByteArray()), null);
        } catch (IOException e) {
            e.printStackTrace();
        }

        return fileName;
    }

    /**
     * 生成并上传带好享免logo二维码
     * @param url
     * @return
     */
    public String uploadQrCodeLogoImage(String url) {

        BufferedImage buffImage = getLogoBufferedImage();
        if (buffImage == null) {
            return "";
        }


        String fileName = "";

        int imageW = 800;
        int imageH = 800;

        QrConfig config = new QrConfig();
        config.setWidth(imageW);
        config.setHeight(imageH);

        config.setImg(buffImage);

        // 高纠错级别
        config.setErrorCorrection(ErrorCorrectionLevel.H);

        BufferedImage generate = QrCodeUtil.generate(url, config);
        try {
            // 转换流信息写出
            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
            ImageIO.write(generate, "png", outputStream);

            fileName = uploadFileToOss(new ByteArrayInputStream(outputStream.toByteArray()), null);
        } catch (IOException e) {
            e.printStackTrace();
        }

        return fileName;
    }

    /**
     * 生成并上传带好享免logo支付，并且有编码显示的二维码
     * @param code 编号
     * @return
     */
    public String uploadQrCodeLogoPayImage(String code){
        BufferedImage buffImage = getLogoBufferedImage();
        if (buffImage == null) {
            return "";
        }

        return uploadQrCode(String.format(valueConfig.getStoreCodeUrl(), code), buffImage, code);
    }

    /**
     * 生成并上传支付码
     * @param url
     * @param buffImage
     * @param numberText
     * @return
     */
    public String uploadQrCode(String url, BufferedImage buffImage, String numberText) {
        String fileName = "";

        int imageW = 800;
        int imageH = 800;

        QrConfig config = new QrConfig();
        config.setWidth(imageW);
        config.setHeight(imageH);

        if (buffImage != null) {
            config.setImg(buffImage);
        }

        // 高纠错级别
        config.setErrorCorrection(ErrorCorrectionLevel.H);
        // config.setErrorCorrection(ErrorCorrectionLevel.Q);

        BufferedImage generate = QrCodeUtil.generate(url, config);
        try {

            // 图片+文字高度
            int textBgHeight = imageH + 50;
            BufferedImage image = new BufferedImage(imageW, textBgHeight, BufferedImage.TYPE_INT_RGB);

            Graphics g = image.createGraphics();
            g.setColor(Color.white);
            // 白色填充整个背景
            g.fillRect(0,0,imageW, textBgHeight);
            // 放入图片
            g.drawImage(generate, 0, 0, imageW, imageH, null);

            // 设置文字
            Font font = new Font("宋体", Font.BOLD, 44);
            g.setFont(font);

            //文字在图片中的坐标 这里设置在中间
            String text = "NO.FREJOYS" + numberText;

            // 获取文字宽度
            int startX = (imageW - g.getFontMetrics(font).stringWidth(text)) / 2;
            int startY = imageH;

            // 设置笔刷蓝色
            // Color color = new Color(23, 150, 247);
            g.setColor(Color.black);

            g.drawString(text, startX, startY);
            g.dispose();

            // 转换流信息写出
            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
            ImageIO.write(image, "png", outputStream);

            fileName = uploadFileToOss(new ByteArrayInputStream(outputStream.toByteArray()), null);
        } catch (IOException e) {
            e.printStackTrace();
        }

        return fileName;
    }


    // 只生成base64，带好享免logo支付，并且有编码显示的二维码
    public String createQrCodeLogoPayImage(String code){
        if (StrUtil.isEmpty(code)) {
            return "";
        }

        BufferedImage buffImage = getLogoBufferedImage();
        if (buffImage == null) {
            return "";
        }

        ByteArrayOutputStream stream = createQrCode(String.format(valueConfig.getStoreCodeUrl(), code), buffImage, code);
        if (stream != null) {
            return Base64.encode(stream.toByteArray());
        }
        return "";
    }

    public ByteArrayOutputStream createQrCode(String url, BufferedImage buffImage, String numberText) {
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();

        int imageW = 800;
        int imageH = 800;

        QrConfig config = new QrConfig();
        config.setWidth(imageW);
        config.setHeight(imageH);

        if (buffImage != null) {
            config.setImg(buffImage);
        }

        // 高纠错级别
        config.setErrorCorrection(ErrorCorrectionLevel.H);
        // config.setErrorCorrection(ErrorCorrectionLevel.Q);

        BufferedImage generate = QrCodeUtil.generate(url, config);
        try {

            // 图片+文字高度
            int textBgHeight = imageH + 50;
            BufferedImage image = new BufferedImage(imageW, textBgHeight, BufferedImage.TYPE_INT_RGB);

            Graphics g = image.createGraphics();
            g.setColor(Color.white);
            // 白色填充整个背景
            g.fillRect(0,0,imageW, textBgHeight);
            // 放入图片
            g.drawImage(generate, 0, 0, imageW, imageH, null);

            // 设置文字
            Font font = new Font("宋体", Font.BOLD, 35);
            g.setFont(font);

            //文字在图片中的坐标 这里设置在中间
            String text = "NO.FREJOYS" + numberText;

            // 获取文字宽度
            int startX = (imageW - g.getFontMetrics(font).stringWidth(text)) / 2;
            int startY = imageH;

            // 设置笔刷蓝色
            // Color color = new Color(23, 150, 247);
            g.setColor(Color.black);

            g.drawString(text, startX, startY);
            g.dispose();

            // 转换流信息写出
            // ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
            ImageIO.write(image, "png", outputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }

        return outputStream;
    }

    /**
     * 验证oss上传回调
     * @param autorization
     * @param xOssPubKeyUrl
     * @param ossCallbackBody
     * @return
     */
    // public boolean verifyOSSCallbackRequest(String uri, String autorization, String xOssPubKeyUrl, String ossCallbackBody) {
    //     byte[] authorization = BinaryUtil.fromBase64String(autorization);
    //     byte[] pubKey = BinaryUtil.fromBase64String(xOssPubKeyUrl);
    //
    //     // 公共key地址 https://gosspublic.alicdn.com/callback_pub_key_v1.pem
    //     String pubKeyAddr = new String(pubKey);
    //
    //     if (!pubKeyAddr.startsWith("https://gosspublic.alicdn.com/") && !pubKeyAddr.startsWith("http://gosspublic.alicdn.com/")) {
    //         return false;
    //     }
    //
    //     String retString = HttpUtil.get(pubKeyAddr);
    //     retString = retString.replace("-----BEGIN PUBLIC KEY-----", "");
    //     retString = retString.replace("-----END PUBLIC KEY-----", "");
    //     // String queryString = "";
    //     // String uri = "/test/oss";
    //     String decodeUri = URLDecoder.decode(uri, StandardCharsets.UTF_8);
    //     try {
    //         java.net.URLDecoder.decode(uri, "UTF-8");
    //     } catch (Exception e) {
    //         e.printStackTrace();
    //     }
    //     String authStr = decodeUri + "\n" + ossCallbackBody;
    //     // if (body != null && !body.equals("")) {
    //     //     authStr += "?" + body;
    //     // }
    //     // authStr += "\n" + ossCallbackBody;
    //
    //     return checkCallback(authStr, authorization, retString);
    // }

    /**
     * 验证RSA
     *
     * @param content
     * @param sign
     * @param publicKey
     * @return
     */
    // public boolean checkCallback(String content, byte[] sign, String publicKey) {
    //     try {
    //         KeyFactory keyFactory = KeyFactory.getInstance("RSA");
    //         byte[] encodedKey = BinaryUtil.fromBase64String(publicKey);
    //         PublicKey pubKey = keyFactory.generatePublic(new X509EncodedKeySpec(encodedKey));
    //         java.security.Signature signature = java.security.Signature.getInstance("MD5withRSA");
    //         signature.initVerify(pubKey);
    //         signature.update(content.getBytes());
    //         boolean bverify = signature.verify(sign);
    //         return bverify;
    //     } catch (Exception e) {
    //         e.printStackTrace();
    //     }
    //     return false;
    // }


}
